1.【預測再驗證|穩定性與記憶體】 地圖畫面要等兩件事:任務資料到了、地圖本身載好了。用 maps-compose 最直覺的骨架大概是這樣:
@Composable
fun TaskMapScreen(viewModel: TaskMapViewModel = hiltViewModel()) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
val cameraPositionState = rememberCameraPositionState()
GoogleMap(
cameraPositionState = cameraPositionState,
onMapLoaded = { viewModel.onMapLoaded() },
) {
state.tasks.forEach { task ->
Marker(state = rememberMarkerState(position = task.position), title = task.name)
}
}
}
@HiltViewModel
class TaskMapViewModel @Inject constructor(repository: TaskRepository) : ViewModel() {
private val mapLoaded = MutableStateFlow(false)
val uiState: StateFlow<TaskMapUiState> = combine(repository.tasks, mapLoaded) { tasks, loaded ->
TaskMapUiState(tasks = if (loaded) tasks else emptyList(), isLoading = !loaded)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), TaskMapUiState())
fun onMapLoaded() { mapLoaded.value = true }
}
下面每個情境,先寫下你的預測與依據,再驗證(maps-compose 的官方範例 App,或本專案的 demo flavor 加一個暫時的地圖畫面;旋轉、「開發者選項 → 不保留活動」、adb shell am kill <package> 都可以用):
onMapLoaded 會再被呼叫嗎?旋轉後那一刻 mapLoaded 的值是什麼?從旋轉到新地圖畫好之間的那段時間,使用者看到什麼?「這一個地圖實例載好了」跟「這個畫面的地圖載好過」是同一件事嗎?onMapLoaded 之後還會不會進來?會的話,mapLoaded.value = true 寫進了誰的 ViewModel、那個 ViewModel 什麼時候被清掉?下次再進地圖畫面,第一幀的 isLoading 是什麼?combine 的 lambda 總共跑幾次?地圖載好那一刻 marker 用的是哪一版資料?把 repository.tasks 從 StateFlow 換成普通 Flow,答案會變嗎?GoogleMap(或 ClusterManager)的欄位,在 MapEffect 裡指派。旋轉三次、離開畫面、等幾秒。LeakCanary 會不會叫?會的話,報告裡 leak 路徑的第一個節點是誰?不會的話,為什麼 ViewModel 握著它沒事?ClusterManager 綁定一個 GoogleMap 實例。旋轉後,舊的 manager 指向誰?用 MapEffect(key1 = …) 重建 manager 時,key 該放什麼?放錯會看到哪一種徵狀:marker 重複、marker 消失,還是點了沒反應?最後回答:這五個情境裡,哪幾個可以在 JVM 單元測試裡用 Turbine 對著 ViewModel 驗?哪幾個要 instrumented test?哪幾個只能實機驗、要寫進發版前的手動檢查清單?
我的回答:(待補)
2.【決策邊界|規格與驗收】 舊 App 的地圖畫面是一個約 490 行的 Fragment,加上兩個畫面共用的 marker 管理(約 220 行)與叢集 renderer(約 105 行)。「就緒」的做法:ViewModel 放一個「可以更新地圖了」的旗標,地圖 callback 進來時設成 true(先檢查 view 還在、fragment 還 attached),onDestroyView 時重設;旗標為 true 之後才開始觀察定位與任務。另一個排序畫面把同一套握手再寫了一次。畫面另外觀察 10 個一次性的操作成功訊息。
新畫面要先決定什麼叫「就緒」。三個選項:
UiState:Loading/Empty/Ready(tasks, bounds)/Error,由 combine(tasks, mapLoaded) 推導;地圖沒載好之前整個畫面蓋一層 loading。不看下面的大綱,依序回答:
Empty 是終態嗎?地圖上要顯示什麼?如果「就緒」的定義裡偷偷含了「任務不是空的」,沒有任務的那一天使用者會看到什麼?把這條寫成驗收條件,而且要讓測試抓得到這個邊界。最後用兩句話收尾:你的結論,以及你願意承認的代價。
我的回答:(待補)